﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace BVM_Alpha
{
    class call
    {
        public string Name;
        public string inst;
        public int Position;
        public Func lastfunc;
        public call()
        {
            Name = "";
            inst = "";
            Position = 0;
            lastfunc = null;
        }
    }

    class field
    {
        public string Name;
        public object Value;
        public field(string name)
        {
            Name = name;
            Value = "";
        }
    }

    class Instance
    {
        public string Name;
        public List<Func> funcs;
        public List<field> fields;
        public Instance(string name)
        {
            Name = name;
            funcs = new List<Func>();
            fields = new List<field>();
        }
    }

    class VirtualMachine
    {
        static Stack<object> stack;
        static vClass currentclass;
        static Func currentfunc;
        static bool CompareFlag;
        static Stack<call> callstack;

        public VirtualMachine()
        {
            stack = new Stack<object>();
            currentclass = null;
            currentfunc = null;
            CompareFlag = false;
            callstack = new Stack<call>();

            loadClass("Program", "Main");
        }

        static void loadClass(string classname, string funcname)
        {
            foreach (vClass c in Program.classes)
            {
                if (c.Name == classname)
                {
                    foreach (Func f in c.funcs)
                    {
                        if (f.Name == funcname)
                        {
                            Program.memory.pos = f.Position;
                            currentclass = c;
                            currentfunc = f;
                            execute();
                        }
                    }
                }
            }
        }

        static void call(string inst, string name)
        {
            foreach (Instance i in currentclass.instanceinfo)
            {
                if (i.Name == inst)
                {
                    foreach (Func f in i.funcs)
                    {
                        if (f.Name == name)
                        {
                            call c = new call();
                            c.Name = currentclass.Name;
                            c.Position = Program.memory.pos;
                            c.inst = inst;
                            c.lastfunc = currentfunc;
                            callstack.Push(c);
                            currentfunc = f;
                            Program.memory.pos = f.Position;
                        }
                    }
                }
            }
        }

        static void calls(string name)
        {
            if (callstack.Count > 0)
            {
                call call = callstack.Pop();
                callstack.Push(call);
                foreach (Instance i in currentclass.instanceinfo)
                {
                    if (i.Name == call.inst)
                    {
                        foreach (Func f in i.funcs)
                        {
                            if (f.Name == name)
                            {
                                call c = new call();
                                c.Name = call.inst;
                                c.Position = Program.memory.pos;
                                c.inst = call.inst;
                                c.lastfunc = currentfunc;
                                callstack.Push(c);
                                currentfunc = f;
                                Program.memory.pos = f.Position;
                            }
                        }
                    }
                }
            }
            else
            {
                foreach (Func f in currentclass.funcs)
                {
                    if (f.Name == name)
                    {
                        call c = new call();
                        c.Name = currentclass.Name;
                        c.Position = Program.memory.pos;
                        c.inst = currentclass.Name;
                        c.lastfunc = currentfunc;
                        callstack.Push(c);
                        currentfunc = f;
                        Program.memory.pos = f.Position;
                    }
                }
            }
        }

        static void jmp(string labelname)
        {
            foreach (Label l in currentfunc.labels)
            {
                if (l.Name == labelname)
                {
                    Program.memory.pos = l.Position;
                }
            }
        }

        static object GetVar(string inst, string name)
        {
            object val = "";
            foreach (Instance i in currentclass.instanceinfo)
            {
                if (i.Name == inst)
                {
                    foreach (field f in i.fields)
                    {
                        if (f.Name == name)
                        {
                            val = f.Value;
                        }
                    }
                }
            }
            return val;
        }

        static string GetString(string name)
        {
            string val = "";
            foreach (String s in Program.vars)
            {
                if (s.baseclass == currentclass.Name)
                {
                    if (s.Name == name)
                    {
                        val = s.Value;
                    }
                }
            }
            return val;
        }

        static int GetInt(string name)
        {
            int val = 0;
            foreach (Int i in Program.vars)
            {
                if (i.baseclass == currentclass.Name)
                {
                    if (i.Name == name)
                    {
                        val = i.Value;
                    }
                }
            }
            return val;
        }

        static object GetObject(string name)
        {
            object val = "";
            foreach (Object o in Program.vars)
            {
                if (o.baseclass == currentclass.Name)
                {
                    if (o.Name == name)
                    {
                        val = o.Value;
                    }
                }
            }
            return val;
        }

        static string GetStringa(string name, int index)
        {
            string val = "";
            foreach (Stringa s in Program.vars)
            {
                if (s.Name == name)
                {
                    if (s.baseclass == currentclass.Name)
                    {
                        if (index > s.Size)
                        {
                        }
                        else
                        {
                            val = s.Value[index];
                        }
                    }
                }
            }
            return val;
        }

        static int GetInta(string name, int index)
        {
            int val = 0;
            foreach (Inta i in Program.vars)
            {
                if (i.baseclass == currentclass.Name)
                {
                    if (i.Name == name)
                    {
                        if (index > i.Size)
                        {
                        }
                        else
                        {
                            val = i.Value[index];
                        }
                    }
                }
            }
            return val;
        }

        static object GetObjecta(string name, int index)
        {
            object val = "";
            foreach (Objecta o in Program.vars)
            {
                if (o.baseclass == currentclass.Name)
                {
                    if (o.Name == name)
                    {
                        if (index > o.Size)
                        {
                        }
                        else
                        {
                            val = o.Value[index];
                        }
                    }
                }
            }
            return val;
        }

        static string GetStringl(string name, int index)
        {
            string val = "";
            foreach (Stringl s in Program.vars)
            {
                if (s.baseclass == currentclass.Name)
                {
                    if (s.Name == name)
                    {
                        val = s.Value[index];
                    }
                }
            }
            return val;
        }

        static int GetIntl(string name, int index)
        {
            int val = 0;
            foreach (Intl i in Program.vars)
            {
                if (i.baseclass == currentclass.Name)
                {
                    if (i.Name == name)
                    {
                        val = i.Value[index];
                    }
                }
            }
            return val;
        }

        static object GetObjectl(string name, int index)
        {
            object val = "";
            foreach (Objectl o in Program.vars)
            {
                if (o.baseclass == currentclass.Name)
                {
                    if (o.Name == name)
                    {
                        val = o.Value[index];
                    }
                }
            }
            return val;
        }

        static void SetVar(string inst, string name, object value)
        {
            foreach (Instance i in currentclass.instanceinfo)
            {
                if (i.Name == inst)
                {
                    foreach (field f in i.fields)
                    {
                        if (f.Name == name)
                        {
                            f.Value = value;
                        }
                    }
                }
            }
        }

        static void SetString(string name, string value)
        {
            foreach (String s in Program.vars)
            {
                if (s.baseclass == currentclass.Name)
                {
                    if (s.Name == name)
                    {
                        s.Value = value;
                    }
                }
            }
        }

        static void SetInt(string name, int value)
        {
            foreach (Int i in Program.vars)
            {
                if (i.baseclass == currentclass.Name)
                {
                    if (i.Name == name)
                    {
                        i.Value = value;
                    }
                }
            }
        }

        static void SetObject(string name, object value)
        {
            foreach (Object o in Program.vars)
            {
                if (o.baseclass == currentclass.Name)
                {
                    if (o.Name == name)
                    {
                        o.Value = value;
                    }
                }
            }
        }

        static void SetStringa(string name, int index, string value)
        {
            foreach (Stringa s in Program.vars)
            {
                if (s.baseclass == currentclass.Name)
                {
                    if (s.Name == name)
                    {
                        if (index > s.Size)
                        {
                        }
                        else
                        {
                            s.Value[index] = value;
                        }
                    }
                }
            }
        }

        static void SetInta(string name, int index, int value)
        {
            foreach (Inta i in Program.vars)
            {
                if (i.baseclass == currentclass.Name)
                {
                    if (i.Name == name)
                    {
                        if (index > i.Size)
                        {
                        }
                        else
                        {
                            i.Value[index] = value;
                        }
                    }
                }
            }
        }

        static void SetObjecta(string name, int index, object value)
        {
            foreach (Objecta o in Program.vars)
            {
                if (o.baseclass == currentclass.Name)
                {
                    if (o.Name == name)
                    {
                        if (index > o.Size)
                        {
                        }
                        else
                        {
                            o.Value[index] = value;
                        }
                    }
                }
            }
        }

        static void SetStringl(string name, string value)
        {
            foreach (Stringl s in Program.vars)
            {
                if (s.baseclass == currentclass.Name)
                {
                    if (s.Name == name)
                    {
                        s.Value.Add(value);
                    }
                }
            }
        }

        static void SetIntl(string name, int value)
        {
            foreach (Intl i in Program.vars)
            {
                if (i.baseclass == currentclass.Name)
                {
                    if (i.Name == name)
                    {
                        i.Value.Add(value);
                    }
                }
            }
        }

        static void SetObjectl(string name, object value)
        {
            foreach (Objectl o in Program.vars)
            {
                if (o.baseclass == currentclass.Name)
                {
                    if (o.Name == name)
                    {
                        o.Value.Add(value);
                    }
                }
            }
        }

        static string GetVarType(string name)
        {
            string type = "";
            foreach (Var v in Program.vars)
            {
                if (v.Name == name)
                {
                    if (v is String)
                    {
                        type = "string";
                    }
                    else if (v is Int)
                    {
                        type = "int";
                    }
                    else if (v is Object)
                    {
                        type = "object";
                    }
                    else if (v is Stringa)
                    {
                        type = "stringa";
                    }
                    else if (v is Inta)
                    {
                        type = "inta";
                    }
                    else if (v is Objecta)
                    {
                        type = "objecta";
                    }
                    else if (v is Stringl)
                    {
                        type = "stringl";
                    }
                    else if (v is Intl)
                    {
                        type = "intl";
                    }
                    else if (v is Objectl)
                    {
                        type = "objectl";
                    }
                }
            }
            return type;
        }

        static void execute()
        {
            byte opcode = 0;
            while (true)
            {
                try
                {
                    try
                    {
                        opcode = Program.memory.Read();
                    }
                    catch
                    {
                    }
                    if (opcode == Opcodes.pushint)
                    {
                        int dim = Program.memory.ReadInt();
                        stack.Push(dim);
                    }
                    else if (opcode == Opcodes.pushstring)
                    {
                        string data = Program.memory.ReadString();
                        stack.Push(data);
                    }
                    else if (opcode == Opcodes.pushfield)
                    {
                        string inst = Program.memory.ReadString();
                        string name = Program.memory.ReadString();
                        if (inst == "self")
                        {
                            call c = callstack.Pop();
                            callstack.Push(c);
                            object val = GetVar(c.inst, name);
                        }
                        else
                        {
                            object val = GetVar(inst, name);
                            stack.Push(val);
                        }
                    }
                    else if (opcode == Opcodes.pushvar)
                    {
                        string name = Program.memory.ReadString();
                        string type = GetVarType(name);
                        if (type == "string")
                        {
                            stack.Push(GetString(name));
                        }
                        else if (type == "int")
                        {
                            stack.Push(GetInt(name));
                        }
                        else if (type == "object")
                        {
                            stack.Push(GetObject(name));
                        }
                        else if (type == "stringa")
                        {
                            int index = Program.memory.ReadInt();
                            stack.Push(GetStringa(name, index));
                        }
                        else if (type == "inta")
                        {
                            int index = Program.memory.ReadInt();
                            stack.Push(GetInta(name, index));
                        }
                        else if (type == "objecta")
                        {
                            int index = Program.memory.ReadInt();
                            stack.Push(GetObjecta(name, index));
                        }
                        else if (type == "stringl")
                        {
                            int index = Program.memory.ReadInt();
                            stack.Push(GetStringl(name, index));
                        }
                        else if (type == "intl")
                        {
                            int index = Program.memory.ReadInt();
                            stack.Push(GetIntl(name, index));
                        }
                        else if (type == "objectl")
                        {
                            int index = Program.memory.ReadInt();
                            stack.Push(GetObjectl(name, index));
                        }
                    }
                    else if (opcode == Opcodes.pop)
                    {
                        stack.Pop();
                    }
                    else if (opcode == Opcodes.popa)
                    {
                        stack.Clear();
                    }
                    else if (opcode == Opcodes.print)
                    {
                        Console.Write(stack.Pop());
                    }
                    else if (opcode == Opcodes.printl)
                    {
                        Console.WriteLine(stack.Pop());
                    }
                    else if (opcode == Opcodes.read)
                    {
                        Console.Read();
                    }
                    else if (opcode == Opcodes.readl)
                    {
                        Console.ReadLine();
                    }
                    else if (opcode == Opcodes.get)
                    {
                        int data = Console.Read();
                        stack.Push(data);
                    }
                    else if (opcode == Opcodes.getl)
                    {
                        string data = Console.ReadLine();
                        stack.Push(data);
                    }
                    else if (opcode == Opcodes.def)
                    {
                        string name = Program.memory.ReadString();
                        string classname = Program.memory.ReadString();
                        foreach (vClass c in Program.classes)
                        {
                            if (c.Name == classname)
                            {
                                Instance i = new Instance(name);
                                foreach (string f in c.fields)
                                {
                                    field field = new field(f);
                                    i.fields.Add(field);
                                }
                                foreach (Func f in c.funcs)
                                {
                                    i.funcs.Add(f);
                                }
                                currentclass.instanceinfo.Add(i);
                            }
                        }
                    }
                    else if (opcode == Opcodes.defstring)
                    {
                        string name = Program.memory.ReadString();
                        String s = new String();
                        s.Name = name;
                        s.baseclass = currentclass.Name;
                        Program.vars.Add(s);
                    }
                    else if (opcode == Opcodes.defint)
                    {
                        string name = Program.memory.ReadString();
                        Int i = new Int();
                        i.Name = name;
                        i.baseclass = currentclass.Name;
                        Program.vars.Add(i);
                    }
                    else if (opcode == Opcodes.defobject)
                    {
                        string name = Program.memory.ReadString();
                        Object o = new Object();
                        o.Name = name;
                        o.baseclass = currentclass.Name;
                        Program.vars.Add(o);
                    }
                    else if (opcode == Opcodes.defstringa)
                    {
                        string name = Program.memory.ReadString();
                        int index = Program.memory.ReadInt();
                        Stringa s = new Stringa(index);
                        s.Name = name;
                        s.baseclass = currentclass.Name;
                        Program.vars.Add(s);
                    }
                    else if (opcode == Opcodes.definta)
                    {
                        string name = Program.memory.ReadString();
                        int index = Program.memory.ReadInt();
                        Inta i = new Inta(index);
                        i.Name = name;
                        i.baseclass = currentclass.Name;
                        Program.vars.Add(i);
                    }
                    else if (opcode == Opcodes.defobjecta)
                    {
                        string name = Program.memory.ReadString();
                        int index = Program.memory.ReadInt();
                        Objecta o = new Objecta(index);
                        o.Name = name;
                        o.baseclass = currentclass.Name;
                        Program.vars.Add(o);
                    }
                    else if (opcode == Opcodes.defstringl)
                    {
                        string name = Program.memory.ReadString();
                        Stringl s = new Stringl();
                        s.Name = name;
                        s.baseclass = currentclass.Name;
                        Program.vars.Add(s);
                    }
                    else if (opcode == Opcodes.defintl)
                    {
                        string name = Program.memory.ReadString();
                        Intl i = new Intl();
                        i.Name = name;
                        i.baseclass = currentclass.Name;
                        Program.vars.Add(i);
                    }
                    else if (opcode == Opcodes.defobjectl)
                    {
                        string name = Program.memory.ReadString();
                        Objectl o = new Objectl();
                        o.Name = name;
                        o.baseclass = currentclass.Name;
                        Program.vars.Add(o);
                    }
                    else if (opcode == Opcodes.setfield)
                    {
                        string inst = Program.memory.ReadString();
                        string name = Program.memory.ReadString();
                        object val = stack.Pop();
                        if (inst == "self")
                        {
                            call c = callstack.Pop();
                            callstack.Push(c);
                            SetVar(c.inst, name, val);
                        }
                        else
                        {
                            SetVar(inst, name, val);
                        }
                    }
                    else if (opcode == Opcodes.setvar)
                    {
                        string name = Program.memory.ReadString();
                        object val = stack.Pop();
                        string type = GetVarType(name);
                        if (type == "string")
                        {
                            SetString(name, val.ToString());
                        }
                        else if (type == "int")
                        {
                            SetInt(name, Int32.Parse(val.ToString()));
                        }
                        else if (type == "object")
                        {
                            SetObject(name, val);
                        }
                        else if (type == "stringa")
                        {
                            int index = Program.memory.ReadInt();
                            SetStringa(name, index, val.ToString());
                        }
                        else if (type == "inta")
                        {
                            int index = Program.memory.ReadInt();
                            SetInta(name, index, Int32.Parse(val.ToString()));
                        }
                        else if (type == "objecta")
                        {
                            int index = Program.memory.ReadInt();
                            SetObjecta(name, index, val);
                        }
                        else if (type == "stringl")
                        {
                            SetStringl(name, val.ToString());
                        }
                        else if (type == "intl")
                        {
                            SetIntl(name, Int32.Parse(val.ToString()));
                        }
                        else if (type == "objectl")
                        {
                            SetObjectl(name, val);
                        }
                    }
                    else if (opcode == Opcodes.add)
                    {
                        object dim1 = stack.Pop();
                        object dim2 = stack.Pop();
                        if (dim1 is string && dim2 is string)
                        {
                            string op = dim1.ToString() + dim2.ToString();
                            stack.Push(op);
                        }
                        else if (dim1 is int && dim2 is int)
                        {
                            int op = Int32.Parse(dim1.ToString()) + Int32.Parse(dim2.ToString());
                            stack.Push(op);
                        }
                    }
                    else if (opcode == Opcodes.sub)
                    {
                        int dim1 = Int32.Parse(stack.Pop().ToString());
                        int dim2 = Int32.Parse(stack.Pop().ToString());
                        int op = dim1 - dim2;
                        stack.Push(op);
                    }
                    else if (opcode == Opcodes.mul)
                    {
                        int dim1 = Int32.Parse(stack.Pop().ToString());
                        int dim2 = Int32.Parse(stack.Pop().ToString());
                        int op = dim1 * dim2;
                        stack.Push(op);
                    }
                    else if (opcode == Opcodes.div)
                    {
                        int dim1 = Int32.Parse(stack.Pop().ToString());
                        int dim2 = Int32.Parse(stack.Pop().ToString());
                        int op = dim1 / dim2;
                        stack.Push(op);
                    }
                    else if (opcode == Opcodes.fwrite)
                    {
                        File.WriteAllText(stack.Pop().ToString(), stack.Pop().ToString(), Encoding.ASCII);
                    }
                    else if (opcode == Opcodes.fread)
                    {
                        string dir = stack.Pop().ToString();
                        int i = Int32.Parse(stack.Pop().ToString());
                        int index = Int32.Parse(stack.Pop().ToString());
                        string text = File.ReadAllText(dir);
                        string dim = text.Substring(i, index);
                        stack.Push(dim);
                    }
                    else if (opcode == Opcodes.fsize)
                    {
                        string dir = stack.Pop().ToString();
                        int length = File.ReadAllText(dir).Length;
                        stack.Push(length);
                    }
                    else if (opcode == Opcodes.cmp)
                    {
                        string val1 = "";
                        string val2 = "";
                        string type1 = Program.memory.ReadString();
                        if (type1 == "field")
                        {
                            string inst = Program.memory.ReadString();
                            string name = Program.memory.ReadString();
                            val1 = GetVar(inst, name).ToString();
                        }
                        else if (type1 == "var")
                        {
                            string name = Program.memory.ReadString();
                            string type = GetVarType(name);
                            if (type == "string")
                            {
                                val1 = GetString(name);
                            }
                            else if (type == "int")
                            {
                                val1 = GetInt(name).ToString();
                            }
                            else if (type == "object")
                            {
                                val1 = GetObject(name).ToString();
                            }
                            else if (type == "stringa")
                            {
                                int index = Program.memory.ReadInt();
                                val1 = GetStringa(name, index);
                            }
                            else if (type == "inta")
                            {
                                int index = Program.memory.ReadInt();
                                val1 = GetInta(name, index).ToString();
                            }
                            else if (type == "objecta")
                            {
                                int index = Program.memory.ReadInt();
                                val1 = GetObjecta(name, index).ToString();
                            }
                            else if (type == "stringl")
                            {
                                int index = Program.memory.ReadInt();
                                val1 = GetStringl(name, index);
                            }
                            else if (type == "intl")
                            {
                                int index = Program.memory.ReadInt();
                                val1 = GetIntl(name, index).ToString();
                            }
                            else if (type == "objectl")
                            {
                                int index = Program.memory.ReadInt();
                                val1 = GetObjectl(name, index).ToString();
                            }
                        }
                        string type2 = Program.memory.ReadString();
                        if (type2 == "field")
                        {
                            string inst = Program.memory.ReadString();
                            string name = Program.memory.ReadString();
                            val2 = GetVar(inst, name).ToString();
                        }
                        else if (type2 == "var")
                        {
                            string name = Program.memory.ReadString();
                            string type = GetVarType(name);
                            if (type == "string")
                            {
                                val2 = GetString(name);
                            }
                            else if (type == "int")
                            {
                                val2 = GetInt(name).ToString();
                            }
                            else if (type == "object")
                            {
                                val2 = GetObject(name).ToString();
                            }
                            else if (type == "stringa")
                            {
                                int index = Program.memory.ReadInt();
                                val2 = GetStringa(name, index).ToString();
                            }
                            else if (type == "inta")
                            {
                                int index = Program.memory.ReadInt();
                                val2 = GetInta(name, index).ToString();
                            }
                            else if (type == "objecta")
                            {
                                int index = Program.memory.ReadInt();
                                val2 = GetObjecta(name, index).ToString();
                            }
                            else if (type == "stringl")
                            {
                                int index = Program.memory.ReadInt();
                                val2 = GetStringl(name, index).ToString();
                            }
                            else if (type == "intl")
                            {
                                int index = Program.memory.ReadInt();
                                val2 = GetIntl(name, index).ToString();
                            }
                            else if (type == "objectl")
                            {
                                int index = Program.memory.ReadInt();
                                val2 = GetObjectl(name, index).ToString();
                            }
                        }
                        if (val1 == val2)
                        {
                            CompareFlag = true;
                        }
                        else
                        {
                            CompareFlag = false;
                        }
                    }
                    else if (opcode == Opcodes.call)
                    {
                        string inst = Program.memory.ReadString();
                        string name = Program.memory.ReadString();
                        call(inst, name);
                    }
                    else if (opcode == Opcodes.calls)
                    {
                        string name = Program.memory.ReadString();
                        calls(name);
                    }
                    else if (opcode == Opcodes.ce)
                    {
                        string inst = Program.memory.ReadString();
                        string name = Program.memory.ReadString();
                        if (CompareFlag)
                        {
                            call(inst, name);
                        }
                    }
                    else if (opcode == Opcodes.ces)
                    {
                        string name = Program.memory.ReadString();
                        if (CompareFlag)
                        {
                            calls(name);
                        }
                    }
                    else if (opcode == Opcodes.cn)
                    {
                        string inst = Program.memory.ReadString();
                        string name = Program.memory.ReadString();
                        if (!CompareFlag)
                        {
                            call(inst, name);
                        }
                    }
                    else if (opcode == Opcodes.cns)
                    {
                        string name = Program.memory.ReadString();
                        if (!CompareFlag)
                        {
                            calls(name);
                        }
                    }
                    else if (opcode == Opcodes.jmp)
                    {
                        string labelname = Program.memory.ReadString();
                        jmp(labelname);
                    }
                    else if (opcode == Opcodes.je)
                    {
                        string labelname = Program.memory.ReadString();
                        if (CompareFlag)
                        {
                            jmp(labelname);
                        }
                    }
                    else if (opcode == Opcodes.jn)
                    {
                        string labelname = Program.memory.ReadString();
                        if (!CompareFlag)
                        {
                            jmp(labelname);
                        }
                    }
                    else if (opcode == Opcodes.exit)
                    {
                        if (callstack.Count > 0)
                        {
                            call c = callstack.Pop();
                            currentfunc = c.lastfunc;
                            Program.memory.pos = c.Position;
                        }
                        else
                        {
                            System.Environment.Exit(0);
                        }
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                    Console.ReadLine();
                }
            }
        }
    }
}
